/*-------------------------------------------------------------------------
 * drawElements Quality Program Test Executor
 * ------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Batch result to XML export.
 *//*--------------------------------------------------------------------*/

#include "xeTestLogParser.hpp"
#include "xeTestResultParser.hpp"
#include "xeXMLWriter.hpp"
#include "xeTestLogWriter.hpp"
#include "deFilePath.hpp"
#include "deString.h"
#include "deStringUtil.hpp"
#include "deCommandLine.hpp"

#include <vector>
#include <string>
#include <map>
#include <cstdio>
#include <fstream>
#include <iostream>

using std::vector;
using std::string;
using std::map;

static const char*	CASELIST_STYLESHEET		= "caselist.xsl";
static const char*	TESTCASE_STYLESHEET		= "testlog.xsl";

enum OutputMode
{
	OUTPUTMODE_SEPARATE = 0,	//!< Separate
	OUTPUTMODE_SINGLE,

	OUTPUTMODE_LAST
};

namespace opt
{

DE_DECLARE_COMMAND_LINE_OPT(OutMode, OutputMode);

void registerOptions (de::cmdline::Parser& parser)
{
	using de::cmdline::Option;
	using de::cmdline::NamedValue;

	static const NamedValue<OutputMode> s_modes[] =
	{
		{ "single",		OUTPUTMODE_SINGLE	},
		{ "separate",	OUTPUTMODE_SEPARATE	}
	};

	parser << Option<OutMode>("m", "mode", "Output mode", s_modes, "single");
}

} // opt

struct CommandLine
{
	CommandLine (void)
		: outputMode(OUTPUTMODE_SINGLE)
	{
	}

	std::string		batchResultFile;
	std::string		outputPath;
	OutputMode		outputMode;
};

static bool parseCommandLine (CommandLine& cmdLine, int argc, const char* const* argv)
{
	de::cmdline::Parser			parser;
	de::cmdline::CommandLine	opts;

	opt::registerOptions(parser);

	if (!parser.parse(argc-1, argv+1, &opts, std::cerr) ||
		opts.getArgs().size() != 2)
	{
		printf("%s: [options] [testlog] [destination path]\n", argv[0]);
		parser.help(std::cout);
		return false;
	}

	cmdLine.outputMode		= opts.getOption<opt::OutMode>();
	cmdLine.batchResultFile	= opts.getArgs()[0];
	cmdLine.outputPath		= opts.getArgs()[1];

	return true;
}

static void parseBatchResult (xe::TestLogParser& parser, const char* filename)
{
	std::ifstream	in			(filename, std::ios_base::binary);
	deUint8			buf[2048];

	for (;;)
	{
		in.read((char*)&buf[0], sizeof(buf));
		int numRead = (int)in.gcount();

		if (numRead > 0)
			parser.parse(&buf[0], numRead);

		if (numRead < (int)sizeof(buf))
			break;
	}
}

// Export to single file

struct BatchResultTotals
{
	BatchResultTotals (void)
	{
		for (int i = 0;i < xe::TESTSTATUSCODE_LAST; i++)
			countByCode[i] = 0;
	}

	int countByCode[xe::TESTSTATUSCODE_LAST];
};

class ResultToSingleXmlLogHandler : public xe::TestLogHandler
{
public:
	ResultToSingleXmlLogHandler (xe::xml::Writer& writer, BatchResultTotals& totals)
		: m_writer	(writer)
		, m_totals	(totals)
	{
	}

	void setSessionInfo (const xe::SessionInfo&)
	{
	}

	xe::TestCaseResultPtr startTestCaseResult (const char* casePath)
	{
		return xe::TestCaseResultPtr(new xe::TestCaseResultData(casePath));
	}

	void testCaseResultUpdated (const xe::TestCaseResultPtr&)
	{
	}

	void testCaseResultComplete (const xe::TestCaseResultPtr& resultData)
	{
		xe::TestCaseResult result;

		xe::parseTestCaseResultFromData(&m_resultParser, &result, *resultData.get());

		// Write result.
		xe::writeTestResult(result, m_writer);

		// Record total
		XE_CHECK(de::inBounds<int>(result.statusCode, 0, xe::TESTSTATUSCODE_LAST));
		m_totals.countByCode[result.statusCode] += 1;
	}

private:
	xe::xml::Writer&		m_writer;
	BatchResultTotals&		m_totals;
	xe::TestResultParser	m_resultParser;
};

static void writeTotals (xe::xml::Writer& writer, const BatchResultTotals& totals)
{
	using xe::xml::Writer;

	int totalCases = 0;

	writer << Writer::BeginElement("ResultTotals");

	for (int code = 0; code < xe::TESTSTATUSCODE_LAST; code++)
	{
		writer << Writer::Attribute(xe::getTestStatusCodeName((xe::TestStatusCode)code), de::toString(totals.countByCode[code]).c_str());
		totalCases += totals.countByCode[code];
	}

	writer << Writer::Attribute("All", de::toString(totalCases).c_str())
		   << Writer::EndElement;
}

static void batchResultToSingleXmlFile (const char* batchResultFilename, const char* dstFileName)
{
	std::ofstream				out			(dstFileName, std::ios_base::binary);
	xe::xml::Writer				writer		(out);
	BatchResultTotals			totals;
	ResultToSingleXmlLogHandler	handler		(writer, totals);
	xe::TestLogParser			parser		(&handler);

	XE_CHECK(out.good());

	out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
		<< "<?xml-stylesheet href=\"" << TESTCASE_STYLESHEET << "\" type=\"text/xsl\"?>\n";

	writer << xe::xml::Writer::BeginElement("BatchResult")
		   << xe::xml::Writer::Attribute("FileName", de::FilePath(batchResultFilename).getBaseName());

	// Parse and write individual cases
	parseBatchResult(parser, batchResultFilename);

	// Write ResultTotals
	writeTotals(writer, totals);

	writer << xe::xml::Writer::EndElement;
	out << "\n";
}

// Export to separate files

class ResultToXmlFilesLogHandler : public xe::TestLogHandler
{
public:
	ResultToXmlFilesLogHandler (vector<xe::TestCaseResultHeader>& resultHeaders, const char* dstPath)
		: m_resultHeaders	(resultHeaders)
		, m_dstPath			(dstPath)
	{
	}

	void setSessionInfo (const xe::SessionInfo&)
	{
	}

	xe::TestCaseResultPtr startTestCaseResult (const char* casePath)
	{
		return xe::TestCaseResultPtr(new xe::TestCaseResultData(casePath));
	}

	void testCaseResultUpdated (const xe::TestCaseResultPtr&)
	{
	}

	void testCaseResultComplete (const xe::TestCaseResultPtr& resultData)
	{
		xe::TestCaseResult result;

		xe::parseTestCaseResultFromData(&m_resultParser, &result, *resultData.get());

		// Write result.
		{
			de::FilePath	casePath	= de::FilePath::join(m_dstPath, (result.casePath + ".xml").c_str());
			std::ofstream	out			(casePath.getPath(), std::ofstream::binary|std::ofstream::trunc);
			xe::xml::Writer	xmlWriter	(out);

			if (!out.good())
				throw xe::Error(string("Failed to open ") + casePath.getPath());

			out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
				<< "<?xml-stylesheet href=\"" << TESTCASE_STYLESHEET << "\" type=\"text/xsl\"?>\n";
			xe::writeTestResult(result, xmlWriter);
			out << "\n";
		}

		m_resultHeaders.push_back(xe::TestCaseResultHeader(result));
	}

private:
	vector<xe::TestCaseResultHeader>&	m_resultHeaders;
	std::string							m_dstPath;
	xe::TestResultParser				m_resultParser;
};

typedef std::map<const xe::TestCase*, const xe::TestCaseResultHeader*> ShortTestResultMap;

static void writeTestCaseListNode (const xe::TestNode* testNode, const ShortTestResultMap& resultMap, xe::xml::Writer& dst)
{
	using xe::xml::Writer;

	bool	isGroup		= testNode->getNodeType() == xe::TESTNODETYPE_GROUP;
	string	fullPath;
	testNode->getFullPath(fullPath);

	if (isGroup)
	{
		const xe::TestGroup* group = static_cast<const xe::TestGroup*>(testNode);

		dst << Writer::BeginElement("TestGroup")
			<< Writer::Attribute("Name", testNode->getName());

		for (int childNdx = 0; childNdx < group->getNumChildren(); childNdx++)
			writeTestCaseListNode(group->getChild(childNdx), resultMap, dst);

		dst << Writer::EndElement;
	}
	else
	{
		DE_ASSERT(testNode->getNodeType() == xe::TESTNODETYPE_TEST_CASE);

		const xe::TestCase*					testCase	= static_cast<const xe::TestCase*>(testNode);
		ShortTestResultMap::const_iterator	resultPos	= resultMap.find(testCase);
		const xe::TestCaseResultHeader*		result		= resultPos != resultMap.end() ? resultPos->second : DE_NULL;

		DE_ASSERT(result);

		dst << Writer::BeginElement("TestCase")
			<< Writer::Attribute("Name", testNode->getName())
			<< Writer::Attribute("Type", xe::getTestCaseTypeName(result->caseType))
			<< Writer::Attribute("StatusCode", xe::getTestStatusCodeName(result->statusCode))
			<< Writer::Attribute("StatusDetails", result->statusDetails.c_str())
			<< Writer::EndElement;
	}
}

static void writeTestCaseList (const xe::TestRoot& root, const ShortTestResultMap& resultMap, xe::xml::Writer& dst)
{
	using xe::xml::Writer;

	dst << Writer::BeginElement("TestRoot");

	for (int childNdx = 0; childNdx < root.getNumChildren(); childNdx++)
		writeTestCaseListNode(root.getChild(childNdx), resultMap, dst);

	dst << Writer::EndElement;
}

static void batchResultToSeparateXmlFiles (const char* batchResultFilename, const char* dstPath)
{
	xe::TestRoot						testRoot;
	vector<xe::TestCaseResultHeader>	shortResults;
	ShortTestResultMap					resultMap;

	// Initialize destination directory.
	if (!de::FilePath(dstPath).exists())
		de::createDirectoryAndParents(dstPath);
	else
		XE_CHECK_MSG(de::FilePath(dstPath).getType() == de::FilePath::TYPE_DIRECTORY, "Destination is not directory");

	// Parse batch result and write out test cases.
	{
		ResultToXmlFilesLogHandler	handler		(shortResults, dstPath);
		xe::TestLogParser			parser		(&handler);

		parseBatchResult(parser, batchResultFilename);
	}

	// Build case hierarchy & short result map.
	{
		xe::TestHierarchyBuilder hierarchyBuilder(&testRoot);

		for (vector<xe::TestCaseResultHeader>::const_iterator result = shortResults.begin(); result != shortResults.end(); result++)
		{
			xe::TestCase* testCase = hierarchyBuilder.createCase(result->casePath.c_str(), result->caseType);
			resultMap.insert(std::make_pair(testCase, &(*result)));
		}
	}

	// Create caselist.
	{
		de::FilePath	indexPath	= de::FilePath::join(dstPath, "caselist.xml");
		std::ofstream	out			(indexPath.getPath(), std::ofstream::binary|std::ofstream::trunc);
		xe::xml::Writer	xmlWriter	(out);

		XE_CHECK_MSG(out.good(), "Failed to open caselist.xml");

		out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
			<< "<?xml-stylesheet href=\"" << CASELIST_STYLESHEET << "\" type=\"text/xsl\"?>\n";
		writeTestCaseList(testRoot, resultMap, xmlWriter);
		out << "\n";
	}
}

int main (int argc, const char* const* argv)
{
	try
	{
		CommandLine cmdLine;
		if (!parseCommandLine(cmdLine, argc, argv))
			return -1;

		if (cmdLine.outputMode == OUTPUTMODE_SINGLE)
			batchResultToSingleXmlFile(cmdLine.batchResultFile.c_str(), cmdLine.outputPath.c_str());
		else
			batchResultToSeparateXmlFiles(cmdLine.batchResultFile.c_str(), cmdLine.outputPath.c_str());
	}
	catch (const std::exception& e)
	{
		printf("%s\n", e.what());
		return -1;
	}

	return 0;
}
